package visitor;

import semantic.Class;
import syntaxtree.*;
import semantic.*;
import java.util.Enumeration;
import java.util.Hashtable;

public class TableCreator implements Visitor {
	
	private Table table = null;
	private Class currClass = null;
	private Method currMethod = null;
	private Hashtable<Identifier,Class> missingClasses;
	private Hashtable<Identifier,Object> missingTypes;
	
	  // MainClass m;
	  // ClassDeclList cl;
	  public Table visit(Program n){
	    n.m.accept(this);
	    
	    table = new Table();
	    missingClasses = new Hashtable<Identifier,Class>();
	    missingTypes = new Hashtable<Identifier,Object>();

	    for ( int i = 0; i < n.cl.size(); i++ ) {
	        n.cl.elementAt(i).accept(this);
	    }
	    //TODO: revisar

	    Enumeration<Identifier> e = missingClasses.keys();
	    while(e.hasMoreElements()){
	    	Identifier i = e.nextElement();
	    	if(table.contains(i)) {
    			ClassExtended clex = (ClassExtended)missingClasses.get(i);
    			clex.setFather(table.get(i));
	    	}
	    	else {
	    		currClass = missingClasses.get(i);
    			String s = "Undefined class " + i.toString() + " at line " + i.line + ", column " + i.column;
    			Errors.complain(s);
	    	}
	    }
	    /*
	    e = missingTypes.keys();
	    while(e.hasMoreElements()){
	    	Identifier i = e.nextElement();
	    	if(!table.contains(i)) {
				Object o = missingTypes.get(i);
				if(o instanceof Class || o instanceof ClassExtended) {
					currClass = (Class)o;
					String s = "Invalid type for " + i.toString() + " at line " + i.line + ", column " + i.column;
					Errors.complain(s);
				}
				if(o instanceof Method) {
					currMethod = (Method)o;
					String s = "Invalid type for " + i.toString() + " at line " + i.line + ", column " + i.column;
					Errors.complain(s);
	
				}
	    	}
	    }
*/
	    
	    //table.print();
	    return table;
	  }
	  
	  // Identifier i1,i2;
	  // Statement s;
	  public void visit(MainClass n) {
	    n.i1.accept(this);
	   
	    n.i2.accept(this);
	    
	    n.s.accept(this);
	   
	  }

	  // Identifier i;
	  // VarDeclList vl;
	  // MethodDeclList ml;
	  public void visit(ClassDeclSimple n) {
	
	    n.i.accept(this);

		if (!table.contains(n.i)) {
			currClass = new Class(n.i);
			table.put(n.i,currClass);

			for ( int i = 0; i < n.vl.size(); i++ ) {

				n.vl.elementAt(i).accept(this);

			}
			for ( int i = 0; i < n.ml.size(); i++ ) {

				n.ml.elementAt(i).accept(this);
			}
			currMethod = null;
		}
		else {
	    	String s = "Double declaration of " + n.i.toString() + " at line " + n.i.line + ", column " + n.i.column;
	    	Errors.complain(s);
	    }
	  }
	 
	  // Identifier i;
	  // Identifier j;
	  // VarDeclList vl;
	  // MethodDeclList ml;
	  public void visit(ClassDeclExtends n) {
		  
		n.i.accept(this);
	    
	    n.j.accept(this);
	    
	    if (!table.contains(n.i)) {
	    	if(table.contains(n.j)) {
	    		currClass = new ClassExtended(n.i, table.get(n.j));
	    	}
	    	else {
	    		currClass = new ClassExtended(n.i, null);
	    		
	    		missingClasses.put(n.j, currClass);
	    	}
	    	
		    table.put(n.i,currClass);	    	
	    		    			   				
		    for ( int i = 0; i < n.vl.size(); i++ ) {
		        
		        n.vl.elementAt(i).accept(this);
		        
		    }
		    	    
		    for ( int i = 0; i < n.ml.size(); i++ ) {
		        
		        n.ml.elementAt(i).accept(this);
		    }
		    currMethod = null;
		    
	    }
	    else {
	    	String s = "Double declaration of " + n.i.toString() + " at line " + n.i.line + ", column " + n.i.column;
	    	Errors.complain(s);
	    }
	  }

	  // Type t;
	  // Identifier i;
	  public void visit(VarDecl n) {
	    n.t.accept(this);
	   
	    n.i.accept(this);
	    
	    if (currMethod == null) {
	    	if (currClass.containsField(n.i)) {
	    		String s = "Double declaration of " + n.i.toString() + " at line " + n.i.line + ", column " + n.i.column;
		    	Errors.complain(s);
	    	}
	    	else {
				if (n.t instanceof IdentifierType) {
					IdentifierType it = (IdentifierType)n.t;
					Identifier idt = new Identifier(it.s, it.line, it.column);
					if (!table.contains(idt)) {
			    		
						missingTypes.put(n.i, currClass); //Um bucket para cada classe não declarada na classe
					}
				}
				currClass.fields.put(n.i, n.t);
			}
	    } 
	    else {
	    	if (currMethod.containsLocal(n.i)) {
	    		String s = "Double declaration of " + n.i.toString() + " at line " + n.i.line + ", column " + n.i.column;
	    		Errors.complain(s);
	    	}
		    else {
		    	if (n.t instanceof IdentifierType) {
					IdentifierType it = (IdentifierType)n.t;
					Identifier idt = new Identifier(it.s, it.line, it.column);
					if (!table.contains(idt)) {
			    		
						missingTypes.put(n.i, currMethod); //Um bucket para cada classe não declarada no método
					}
				}
				currMethod.locals.put(n.i, n.t);
		    }
	    }
	 }

	  // Type t;
	  // Identifier i;
	  // FormalList fl;
	  // VarDeclList vl;
	  // StatementList sl;
	  // Exp e;
	  public void visit(MethodDecl n) {
	   
	    n.t.accept(this);
	   
	    n.i.accept(this);
	    
	    currMethod = new Method(n.i, n.t, currClass);
	    currClass.methods.put(n.i,currMethod);
	    
	    for ( int i = 0; i < n.fl.size(); i++ ) {
	        n.fl.elementAt(i).accept(this);
	        
	    }
	    
	    for ( int i = 0; i < n.vl.size(); i++ ) {
	        
	        n.vl.elementAt(i).accept(this);
	        
	    }
	    for ( int i = 0; i < n.sl.size(); i++ ) {
	        
	        n.sl.elementAt(i).accept(this);
	        
	    }
	   
	    n.e.accept(this);
	    
	  }

	  // Type t;
	  // Identifier i;
	  public void visit(Formal n) {
	    n.t.accept(this);
	    
	    n.i.accept(this);
	    
	    currMethod.params.put(n.i, n.t);
	    currMethod.paramsTypes.add(n.t);
	  }

	  public void visit(IntArrayType n) {
	    
	  }

	  public void visit(BooleanType n) {
	    
	  }

	  public void visit(IntegerType n) {
	    
	  }

	  // String s;
	  public void visit(IdentifierType n) {
	   
	  }

	  // StatementList sl;
	  public void visit(Block n) {
	    
	    for ( int i = 0; i < n.sl.size(); i++ ) {
	        
	        n.sl.elementAt(i).accept(this);
	        
	    }
	  }

	  // Exp e;
	  // Statement s1,s2;
	  public void visit(If n) {
	   
	    n.e.accept(this);
	    
	    n.s1.accept(this);
	   
	    n.s2.accept(this);
	  }

	  // Exp e;
	  // Statement s;
	  public void visit(While n) {
	   
	    n.e.accept(this);
	    
	    n.s.accept(this);
	  }

	  // Exp e;
	  public void visit(Print n) {
	    
	    n.e.accept(this);
	    
	  }
	  
	  // Identifier i;
	  // Exp e;
	  public void visit(Assign n) {
	    n.i.accept(this);
	   
	    n.e.accept(this);
	    
	  }

	  // Identifier i;
	  // Exp e1,e2;
	  public void visit(ArrayAssign n) {
	    n.i.accept(this);
	   
	    n.e1.accept(this);
	    
	    n.e2.accept(this);
	    
	  }

	  // Exp e1,e2;
	  public void visit(And n) {
	   
	    n.e1.accept(this);
	    
	    n.e2.accept(this);
	    
	  }

	  // Exp e1,e2;
	  public void visit(LessThan n) {
	    
	    n.e1.accept(this);
	   
	    n.e2.accept(this);
	    
	  }

	  // Exp e1,e2;
	  public void visit(Plus n) {
	    
	    n.e1.accept(this);
	    
	    n.e2.accept(this);
	    
	  }

	  // Exp e1,e2;
	  public void visit(Minus n) {
	    
	    n.e1.accept(this);
	    
	    n.e2.accept(this);
	    
	  }

	  // Exp e1,e2;
	  public void visit(Times n) {
	    
	    n.e1.accept(this);
	    
	    n.e2.accept(this);
	    
	  }

	  // Exp e1,e2;
	  public void visit(ArrayLookup n) {
	    n.e1.accept(this);
	   
	    n.e2.accept(this);
	    
	  }

	  // Exp e;
	  public void visit(ArrayLength n) {
	    n.e.accept(this);
	    
	  }

	  // Exp e;
	  // Identifier i;
	  // ExpList el;
	  public void visit(Call n) {
	    n.e.accept(this);
	   
	    n.i.accept(this);
	    
	    for ( int i = 0; i < n.el.size(); i++ ) {
	        n.el.elementAt(i).accept(this);
	        
	    }
	   
	  }

	  // int i;
	  public void visit(IntegerLiteral n) {
	    
	  }

	  public void visit(True n) {
	   
	  }

	  public void visit(False n) {
	   
	  }

	  // String s;
	  public void visit(IdentifierExp n) {
	    
	  }

	  public void visit(This n) {
	   
	  }

	  // Exp e;
	  public void visit(NewArray n) {
	   
	    n.e.accept(this);
	   
	  }

	  // Identifier i;
	  public void visit(NewObject n) {
	   
	  }

	  // Exp e;
	  public void visit(Not n) {
	    n.e.accept(this);
	  }

	  // String s;
	  public void visit(Identifier n) {
		  
	  }
}
